package com.yanyeori.framework.core.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.yanyeori.framework.core.constant.BaseConstant;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * HttpUtil
 *
 * @author chenkuan
 * @since 2022/9/6
 */
public class HttpUtil {

    private final HttpConfig httpConfig;

    private HttpUtil(HttpConfig httpConfig) {
        this.httpConfig = httpConfig;
    }

    public static HttpConfig builder() {
        return new HttpConfig();
    }

    public HttpTask http(String url) {
        return new HttpTask(httpConfig, url);
    }

    public static class HttpConfig {
        private static volatile RestTemplate defaultRestTemplate;
        private RestTemplate restTemplate;
        private String baseUrl;

        public HttpUtil build() {
            if (restTemplate == null) {
                restTemplate = getDefaultRestTemplate();
            }
            return new HttpUtil(this);
        }

        public HttpConfig setRestTemplate(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
            return this;
        }

        public HttpConfig setBaseUrl(String baseUrl) {
            this.baseUrl = baseUrl;
            return this;
        }

        private static RestTemplate getDefaultRestTemplate() {
            if (defaultRestTemplate == null) {
                synchronized (HttpConfig.class) {
                    if (defaultRestTemplate == null) {
                        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
                        factory.setConnectTimeout(60000);
                        factory.setReadTimeout(0);
                        defaultRestTemplate = new RestTemplate(factory);
                    }
                }
            }
            return defaultRestTemplate;
        }
    }

    public static class HttpTask {
        private final HttpConfig httpConfig;
        private final String url;
        private final Map<String, String> headers = new HashMap<>();
        private MediaType contentType = MediaType.APPLICATION_JSON;
        private final Map<String, Object> urlParams = new HashMap<>();
        private Object[] uriVariables = {};
        private Object body;

        private HttpTask(HttpConfig httpConfig, String url) {
            this.httpConfig = httpConfig;
            url = StringUtil.defaultIfEmpty(httpConfig.baseUrl, "") + StringUtil.defaultIfEmpty(url, "");
            if (StringUtils.isEmpty(url)) {
                throw new IllegalArgumentException("The URL cannot be empty");
            }
            this.url = url;
        }

        public HttpResult put() {
            return exchange(HttpMethod.PUT, new HttpEntity<>(body, buildHeaders()));
        }

        public HttpResult get() {
            return exchange(HttpMethod.GET, new HttpEntity<>(null, buildHeaders()));
        }

        public HttpResult post() {
            return exchange(HttpMethod.POST, new HttpEntity<>(body, buildHeaders()));
        }

        private HttpResult exchange(HttpMethod httpMethod, HttpEntity<?> httpEntity) {
            String uri = buildUrlParams(url, urlParams);
            ResponseEntity<String> responseEntity = httpConfig.restTemplate.exchange(uri, httpMethod, httpEntity, String.class, uriVariables);
            return new HttpResult(responseEntity);
        }

        public HttpTask setHeaders(Map<String, String> headers) {
            if (MapUtils.isNotEmpty(headers)) {
                this.headers.putAll(headers);
            }
            return this;
        }

        public HttpTask setHeaders(String name, String value) {
            if (name != null && value != null) {
                this.headers.put(name, value);
            }
            return this;
        }

        public void setContentType(MediaType contentType) {
            this.contentType = contentType;
        }

        public HttpTask setUrlParams(Map<String, Object> urlParams) {
            if (MapUtils.isNotEmpty(urlParams)) {
                this.urlParams.putAll(urlParams);
            }
            return this;
        }

        public HttpTask setUrlParams(String name, Object value) {
            if (name != null && value != null) {
                this.urlParams.put(name, value);
            }
            return this;
        }

        public void setUriVariables(Object... uriVariables) {
            this.uriVariables = uriVariables;
        }

        public HttpTask setBody(Object body) {
            this.body = body;
            return this;
        }

        private HttpHeaders buildHeaders() {
            HttpHeaders httpHeaders = new HttpHeaders();
            if (MapUtils.isNotEmpty(headers)) {
                headers.forEach(httpHeaders::add);
            }
            httpHeaders.setContentType(contentType);
            return httpHeaders;
        }
    }

    public static class HttpResult {
        private final ResponseEntity<String> response;

        private HttpResult(ResponseEntity<String> responseEntity) {
            this.response = responseEntity;
        }

        public <T> T toBean(Class<T> tClass) {
            return getBody() == null ? null : JacksonUtil.parseObject(getBody(), tClass);
        }

        public <T> T toBean(TypeReference<T> type) {
            return getBody() == null ? null : JacksonUtil.parseObject(getBody(), type);
        }

        public <T> List<T> toList(Class<T> tClass) {
            return getBody() == null ? null : JacksonUtil.parseArray(getBody(), tClass);
        }

        public ResponseEntity<String> getResponse() {
            return response;
        }

        public int getStatus() {
            return response.getStatusCodeValue();
        }

        public String getBody() {
            return response.getBody();
        }
    }

    /**
     * 下载文件
     *
     * @param url 文件链接
     * @return InputStream 字节流
     */
    public static InputStream downloadFile(String url) {
        try {
            URL request = new URL(url);
            URLConnection connection = request.openConnection();
            connection.setConnectTimeout(60000);
            return connection.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 下载文件
     *
     * @param url       文件链接
     * @param destFile  目标写入文件
     * @param overwrite 是否覆盖（否则重命名）
     * @return File 返回最终写入的文件
     */
    public static File downloadFile(String url, File destFile, boolean overwrite) {
        InputStream inputStream = downloadFile(url);
        return FileUtil.write(inputStream, destFile.getParent(), destFile.getName(), overwrite);
    }

    /**
     * 下载文件，默认覆盖同名文件
     *
     * @param url      文件链接
     * @param destFile 目标写入文件
     * @return File 返回最终写入的文件
     */
    public static File downloadFile(String url, File destFile) {
        return downloadFile(url, destFile, true);
    }

    /**
     * url拼接参数key=value
     *
     * @param url      url
     * @param paramMap 参数key-value map
     */
    public static String buildUrlParams(String url, Map<String, ?> paramMap) {
        if (MapUtils.isEmpty(paramMap)) {
            return url;
        }
        StringBuilder uri = new StringBuilder(url);
        for (Map.Entry<String, ?> entry : paramMap.entrySet()) {
            uri.append(uri.lastIndexOf("?") == -1 ? "?" : "&").append(entry.getKey()).append("=").append(entry.getValue());
        }
        return uri.toString();
    }

    /**
     * encode url 编码
     */
    public static String encodeUrl(String code) {
        try {
            return URLEncoder.encode(code, BaseConstant.UTF_8);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * decode url 解码
     */
    public static String decodeUrl(String code) {
        try {
            return URLDecoder.decode(code, BaseConstant.UTF_8);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取客户端IP
     *
     * @param request          请求对象
     * @param otherHeaderNames 其他自定义header
     * @return IP地址
     */
    public static String getClientIP(HttpServletRequest request, String... otherHeaderNames) {
        String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        if (ArrayUtils.isNotEmpty(otherHeaderNames)) {
            headers = ArrayUtils.addAll(headers, otherHeaderNames);
        }

        String ip;
        for (String header : headers) {
            ip = request.getHeader(header);
            if (!isUnknow(ip)) {
                return getMultistageReverseProxyIp(ip);
            }
        }

        ip = request.getRemoteAddr();
        return getMultistageReverseProxyIp(ip);
    }

    /**
     * 从多级反向代理中获得第一个非unknown IP地址
     *
     * @param ip 获得的IP地址
     * @return 第一个非unknown IP地址
     */
    public static String getMultistageReverseProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (!isUnknow(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return ip;
    }

    /**
     * 检测给定字符串是否为未知，多用于检测HTTP请求相关
     *
     * @param checkString 被检测的字符串
     * @return 是否未知
     */
    public static boolean isUnknow(String checkString) {
        return StringUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
    }
}
